
package com.itheima.easy.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.json.JSONUtil;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.itheima.easy.base.PageResponse;
import com.itheima.easy.dto.BillDto;
import com.itheima.easy.dto.PrepaidRechargeRecordDto;
import com.itheima.easy.dto.RetreatClearingBillDto;
import com.itheima.easy.entity.*;
import com.itheima.easy.enums.BillStatus;
import com.itheima.easy.enums.BillType;
import com.itheima.easy.exception.BaseException;
import com.itheima.easy.handler.wechat.WechatWapPayHandler;
import com.itheima.easy.mapper.BillMapper;
import com.itheima.easy.mapper.FundFlowMapper;
import com.itheima.easy.mapper.PrepaidRechargeRecordMapper;
import com.itheima.easy.service.*;
import com.itheima.easy.utils.CodeUtil;
import com.itheima.easy.utils.ObjectUtil;
import com.itheima.easy.utils.UserThreadLocal;
import com.itheima.easy.vo.*;
import com.itheima.easy.vo.retreat.Arrearage;
import com.itheima.easy.vo.retreat.DueBack;
import com.itheima.easy.vo.retreat.RetreatBillVo;
import com.itheima.easy.vo.retreat.Unpaid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@Service
public class BillServiceImpl implements BillService {

    @Resource
    private BillMapper billMapper;

    @Resource
    private BalanceService balanceService;

    @Resource
    private CheckInConfigService checkInConfigService;

    @Resource
    private PrepaidRechargeRecordMapper prepaidRechargeRecordMapper;

    @Resource
    private FundFlowMapper fundFlowMapper;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private TradingService tradingService;

    @Resource
    private RefundRecordService refundRecordService;

    @Resource
    private WechatWapPayHandler wechatWapPayHandler;

    @Resource
    private MemberService memberService;

    @Resource
    private MemberElderService memberElderService;

    /**
     * 根据id删除账单
     * @param id 账单id
     * @return 删除结果
     */
    @Override
    public int deleteByPrimaryKey(Long id) {
        return billMapper.deleteByPrimaryKey(id);
    }

    /**
     * 生成月度账单
     * @param billDto 账单实体
     * @return 插入结果
     */
    @Transactional
    @Override
    public int createMonthBill(BillDto billDto) {
        Bill bill = BeanUtil.toBean(billDto, Bill.class);
        // 生成账单
        bill.setBillType(BillType.MONTH.getOrdinal());
        bill.setTransactionStatus(BillStatus.UN_PAY.getOrdinal());
        // 编号
        String zd = CodeUtil.generateCode("ZD", stringRedisTemplate, 5);
        bill.setBillNo(zd);
        // 查询入住配置
        CheckInConfig checkInConfig = checkInConfigService.findCurrentConfigByElderId(bill.getElderId());
        // 账单开始结束时间
        int year = Integer.parseInt(bill.getBillMonth().substring(0, 4));
        int monthOfYear = Integer.parseInt(bill.getBillMonth().substring(5, 7));

        LocalDateTime firstDayOfMonth = LocalDateTime.of(year, monthOfYear, 1, 0, 0, 0);
        if (checkInConfig.getCheckInEndTime().isBefore(firstDayOfMonth)) {
            throw new BaseException("该月不在入住期限内");
        }
        // 押金
        BigDecimal depositAmount = new BigDecimal(0);
        if (checkInConfig.getCheckInStartTime().isAfter(firstDayOfMonth)) {
            // 首月
            bill.setBillStartTime(checkInConfig.getCheckInStartTime());
            depositAmount = checkInConfig.getDepositAmount();
        } else {
            bill.setBillStartTime(firstDayOfMonth);
        }
        // 支付截止时间
        bill.setPaymentDeadline(bill.getBillStartTime().plusDays(6));
        // 押金金额
        bill.setDepositAmount(depositAmount);

        LocalDateTime lastDayOfMonth = firstDayOfMonth.withDayOfMonth(firstDayOfMonth.toLocalDate().lengthOfMonth())
                .withHour(23)
                .withMinute(59)
                .withSecond(59);

        if (checkInConfig.getCheckInEndTime().isAfter(lastDayOfMonth)) {
            bill.setBillEndTime(lastDayOfMonth);
        } else {
            // 末月
            bill.setBillEndTime(checkInConfig.getCheckInEndTime());
        }

        // 费用共计天数
        int i = bill.getBillEndTime().getDayOfMonth() - bill.getBillStartTime().getDayOfMonth();
        bill.setTotalDays(i);
        // 每月应付
        BigDecimal cost = checkInConfig.getBedCost().add(checkInConfig.getOtherCost()).add(checkInConfig.getNursingCost())
                .subtract(checkInConfig.getMedicalInsurancePayment()).subtract(checkInConfig.getGovernmentSubsidy());

        // 当月天数
        int days = lastDayOfMonth.getDayOfMonth() - firstDayOfMonth.getDayOfMonth();
        // 本期应付 = （每月应付 / 当月天数）* 共计天数
        // 首月和最后一个月需要扣减
        BigDecimal currentCost = cost.divide(new BigDecimal(days)).multiply(new BigDecimal(i));
        bill.setCurrentCost(currentCost);
        // 账单金额 = 本期应付 + 押金
        BigDecimal billAmount = currentCost.add(depositAmount);
        bill.setBillAmount(billAmount);
        // 应付金额 = 本期应付 - 预交款
        // 查询余额表
        Balance balance = balanceService.selectByElderId(bill.getElderId());
        if (balance.getPrepaidBalance().compareTo(billAmount) > 0) {
            // 预交款足够 应付金额 = 0
            bill.setPayableAmount(new BigDecimal(0));
            // 预交款扣减
            balance.setPrepaidBalance(balance.getPrepaidBalance().subtract(billAmount));
            bill.setPrepaidAmount(billAmount);
            bill.setTransactionStatus(BillStatus.PAY.ordinal());

        } else {
            // 预交款不足
            BigDecimal payableAmount = billAmount.subtract(balance.getPrepaidBalance());
            bill.setPayableAmount(payableAmount);
            // 预交款扣减
            balance.setPrepaidBalance(new BigDecimal(0));
            bill.setPrepaidAmount(balance.getPrepaidBalance());
            bill.setTransactionStatus(BillStatus.UN_PAY.ordinal());

        }
        // 预交款扣减后回写数据库
        balanceService.save(balance);
        return billMapper.insert(bill);
    }

    /**
     * 生成费用账单
     * @param billDto 账单实体
     * @return 插入结果
     */
    @Override
    public int createProjectBill(BillDto billDto) {
        Bill bill = BeanUtil.toBean(billDto, Bill.class);
        // 生成账单
        bill.setBillType(BillType.PROJECT.getOrdinal());
        bill.setTransactionStatus(BillStatus.UN_PAY.getOrdinal());
        billDto.setBillAmount(billDto.getPayableAmount());
        // 编号
        String zd = CodeUtil.generateCode("ZD", stringRedisTemplate, 5);
        bill.setBillNo(zd);
        bill.setCurrentCost(bill.getBillAmount());
        bill.setPrepaidAmount(bill.getBillAmount());
        bill.setDepositAmount(bill.getBillAmount());
        bill.setPayableAmount(bill.getBillAmount());
        bill.setPaymentDeadline(LocalDateTime.now().minusMinutes(15));
        return billMapper.insert(bill);
    }


    /**
     * 插入账单
     * @param record 账单实体
     * @return 插入结果
     */
    @Override
    public int insertSelective(Bill record) {
        return 0;
    }

    @Override
    public BillVo selectByPrimaryKey(Long id) {
        Bill bill = billMapper.selectByPrimaryKey(id);
        return BeanUtil.toBean(bill, BillVo.class);
    }

    @Override
    public int updateBytradingOrderNoSelective(BillDto billDto) {
        Bill bill = BeanUtil.toBean(billDto, Bill.class);
        return billMapper.updateBytradingOrderNoSelective(bill);
    }

    @Override
    public int cancelById(BillDto billDto) {
        Bill bill = BeanUtil.toBean(billDto, Bill.class);
        return billMapper.updateByPrimaryKey(bill);
    }

    /**
     * 退住结算查询
     * @param elderId 老人ID
     * @param localDateTime 实际退住时间
     * @return 退住结算详情
     */
    @Override
    public RetreatBillVo retreatSettlement(Long elderId, LocalDateTime localDateTime) {
        RetreatBillVo retreatBillVo = new RetreatBillVo();
        // 应退
        List<Bill> dueBackBills = billMapper.selectDueBackByElder(elderId, BillStatus.PAY.getOrdinal(), localDateTime);
        List<DueBack> dueBacks = dueBackBills.stream().map(v -> {
            DueBack dueBack = new DueBack();
            dueBack.setCode(v.getBillNo());
            dueBack.setType(v.getBillType());
            dueBack.setBillMonth(v.getBillMonth());
            dueBack.setAmount(v.getBillAmount());
            dueBack.setTradingOrderNo(v.getTradingOrderNo());
            // 备注是护理项目名称
            dueBack.setNursingName(v.getRemark());
            return dueBack;
        }).collect(Collectors.toList());
        retreatBillVo.setDueBackList(dueBacks);

        // 欠费
        List<Bill> arrearageBills = billMapper.selectArrearageByElder(elderId, BillStatus.UN_PAY.getOrdinal(), localDateTime);
        List<Arrearage> arrearages = arrearageBills.stream().map(v -> {
            Arrearage arrearage = new Arrearage();
            arrearage.setCode(v.getBillNo());
            arrearage.setBillMonth(v.getBillMonth());
            arrearage.setAmount(v.getBillAmount());
            return arrearage;
        }).collect(Collectors.toList());
        retreatBillVo.setArrearageList(arrearages);

        // 余额
        Balance balance = balanceService.selectByElderId(elderId);
        retreatBillVo.setBalanceVo(BeanUtil.toBean(balance, BalanceVo.class));
        // 未缴
        List<Bill> unpaidBills = billMapper.selectUnpaidByElder(elderId, BillStatus.UN_PAY.getOrdinal());
        List<Unpaid> unpaids = unpaidBills.stream().map(v -> {
            Unpaid unpaid = new Unpaid();
            unpaid.setCode(v.getBillNo());
            unpaid.setType(v.getBillType());
            unpaid.setAmount(v.getBillAmount());
            // 备注是护理项目名称
            unpaid.setNursingName(v.getRemark());
            return unpaid;
        }).collect(Collectors.toList());
        retreatBillVo.setUnpaidList(unpaids);
        return retreatBillVo;
    }

    /**
     * 退住清算
     *
     * @return 退住清算详情
     */
    @Transactional
    @Override
    public void retreatClearingBill(RetreatClearingBillDto retreatClearingBillDto) {
        log.info(JSONUtil.toJsonStr(retreatClearingBillDto));
        // 押金转预交款
        Balance balance = balanceService.selectByElderId(retreatClearingBillDto.getElderId());
        BigDecimal prepaid = balance.getPrepaidBalance().add(balance.getDepositAmount()).subtract(retreatClearingBillDto.getDepositDeductions());

        // 应退转预交款
        List<Bill> dueBackBills = billMapper.selectDueBackByElder(retreatClearingBillDto.getElderId(), BillStatus.PAY.getOrdinal(), retreatClearingBillDto.getLocalDateTime());
        dueBackBills.forEach(bill -> {
            bill.setTransactionStatus(BillStatus.CLOSE.getOrdinal());
            billMapper.updateByIdSelective(bill);

            // 账单退款记录
            RefundRecord refundRecord = new RefundRecord();
            refundRecord.setTradingOrderNo(bill.getTradingOrderNo());
            refundRecord.setRemark(retreatClearingBillDto.getRemark());
            refundRecord.setMemo(retreatClearingBillDto.getDay() + "");
            refundRecord.setCreateBy(retreatClearingBillDto.getUserId());

            // 押金扣减备注
            balance.setRemark(retreatClearingBillDto.getDepositRemark());

            if (bill.getBillNo().equals(retreatClearingBillDto.getBillNo())) {
                BigDecimal subtract = bill.getPayableAmount().subtract(retreatClearingBillDto.getBillDeductions());
                prepaid.add(subtract);
                refundRecord.setRefundAmount(subtract);
                return;
            }
            prepaid.add(bill.getPayableAmount());
            refundRecord.setRefundAmount(bill.getPayableAmount());
            refundRecordService.saveOrUpdate(refundRecord);
        });
        balance.setPrepaidBalance(prepaid);

        // 预交款抵扣欠费账单
        List<Bill> arrearageBills = billMapper.selectArrearageByElder(retreatClearingBillDto.getElderId(), BillStatus.UN_PAY.getOrdinal(), retreatClearingBillDto.getLocalDateTime());
        arrearageBills.forEach(bill -> {

            BigDecimal billAmount = bill.getBillAmount();
            if (balance.getPrepaidBalance().compareTo(billAmount) > 0) {
                // 预交款足够 应付金额 = 0
                bill.setPayableAmount(new BigDecimal(0));
                // 预交款扣减
                balance.setPrepaidBalance(balance.getPrepaidBalance().subtract(billAmount));
                bill.setPrepaidAmount(billAmount);
                bill.setTransactionStatus(BillStatus.PAY.ordinal());
                billMapper.updateByIdSelective(bill);
                return;
            }

            // 扣完
            if (balance.getPrepaidBalance().equals(new BigDecimal(0))) {
                return;
            }

            // 预交款不足
            BigDecimal payableAmount = billAmount.subtract(balance.getPrepaidBalance());
            bill.setPayableAmount(payableAmount);
            // 预交款扣减
            balance.setPrepaidBalance(new BigDecimal(0));
            bill.setPrepaidAmount(balance.getPrepaidBalance());
            bill.setTransactionStatus(BillStatus.UN_PAY.ordinal());
            billMapper.updateByIdSelective(bill);

        });
        balanceService.save(balance);
    }


    /**
     * 分页查询账单
     * @param billNo 账单编号
     * @param elderName 老人姓名
     * @param elderIdCard 老人身份证号
     * @param billMonth 账单月份
     * @param pageNum 页码
     * @param pageSize 每页数量
     * @return 分页结果
     */
    @Override
    public PageResponse<BillVo> getBillPage(String billNo, String elderName, String elderIdCard, String billMonth, Integer transactionStatus, Long elderId, Integer pageNum, Integer pageSize) {
        PageHelper.startPage(pageNum, pageSize);
        List<Long> elderIds = null;
        if (ObjectUtil.isNotEmpty(elderId)) {
            elderIds = Arrays.asList(elderId);
        } else {
            Long userId = UserThreadLocal.getUserId();
            if (ObjectUtil.isNotEmpty(userId)) {
                List<MemberElderVo> memberElderVos = memberElderService.listByMemberId(userId);
                elderIds = memberElderVos.stream().map(MemberElderVo::getElderId).distinct().collect(Collectors.toList());
            }
        }

        Page<BillVo> page = billMapper.page(billNo, elderName, elderIdCard, billMonth, transactionStatus, elderIds);
        return PageResponse.of(page, BillVo.class);
    }

    /**
     * 查询欠费账单
     * @param bedNo 床位号
     * @param elderName 老人姓名
     * @param pageNum 页码
     * @param pageSize 每页数量
     * @return 分页结果
     */
    @Override
    public PageResponse<BillVo> arrears(String bedNo, String elderName, Integer pageNum, Integer pageSize) {
        PageHelper.startPage(pageNum, pageSize);
        Page<BillVo> page =  billMapper.arrears(bedNo, elderName);
        return PageResponse.of(page, BillVo.class);
    }

    /**
     * 保存预交款充值记录
     * @param dto 预交款充值记录实体
     */
    @Transactional
    @Override
    public void savePrepaidRechargeRecord(PrepaidRechargeRecordDto dto) {
        PrepaidRechargeRecord prepaidRechargeRecord = BeanUtil.toBean(dto, PrepaidRechargeRecord.class);
        String yj = CodeUtil.generateCode("YJ", stringRedisTemplate, 5);
        prepaidRechargeRecord.setPrepaidRechargeNo(yj);
        prepaidRechargeRecordMapper.insert(prepaidRechargeRecord);
        Balance balance = balanceService.selectByElderId(prepaidRechargeRecord.getElderId());
        balance.setPrepaidBalance(balance.getPrepaidBalance().add(prepaidRechargeRecord.getRechargeAmount()));
        balanceService.save(balance);
    }

    /**
     * 分页查询预交款充值记录
     * @param bedNo 床位号
     * @param elderName 老人姓名
     * @param pageNum 页码
     * @param pageSize 每页数量
     * @return 分页结果
     */
    @Override
    public PageResponse<PrepaidRechargeRecordVo> prepaidRechargeRecordPage(String bedNo, String elderName, Integer pageNum, Integer pageSize) {
        PageHelper.startPage(pageNum, pageSize);
        Page<PrepaidRechargeRecord> page =  prepaidRechargeRecordMapper.prepaidRechargeRecordPage(bedNo, elderName);
        return PageResponse.of(page, PrepaidRechargeRecordVo.class);
    }


    /**
     * 分页查询资金流水
     * @param bedNo 床位号
     * @param elderName 老人姓名
     * @param direction 资金流向
     * @param balanceType 资金类型
     * @param pageNum 页码
     * @param pageSize 每页数量
     * @return 分页结果
     */
    @Override
    public PageResponse<FundFlowVo> fundFlowPage(String bedNo, String elderName, Integer direction, Integer balanceType, Integer pageNum, Integer pageSize) {
        PageHelper.startPage(pageNum, pageSize);
        Page<FundFlow> page =  fundFlowMapper.page(bedNo, elderName, direction, balanceType);
        return PageResponse.of(page, FundFlowVo.class);
    }

    /**
     * 账单线下支付记录
     * @param tradingVo
     */
    @Override
    public void payRecord(TradingVo tradingVo) {
        Trading trading = BeanUtil.toBean(tradingVo, Trading.class);
        tradingService.saveOrUpdate(trading);
    }

    /**
     * 线上支付
     * @param billDto
     */
    @Override
    public TradingVo pay(BillDto billDto) {
        Bill bill = BeanUtil.toBean(billDto, Bill.class);
        // 交易单
        TradingVo tradingVo = new TradingVo();
        tradingVo.setMemo("服务下单");
        Long userId = UserThreadLocal.getUserId();
        if (ObjectUtil.isNotEmpty(userId)) {
            Member byId = memberService.getById(userId);
            tradingVo.setOpenId(byId.getOpenId());
        }
        tradingVo.setTradingType("1");
        tradingVo.setTradingAmount(bill.getPayableAmount());
        tradingVo.setProductOrderNo(bill.getId());
        TradingVo tradingVo1 = wechatWapPayHandler.wapTrading(tradingVo);

        bill.setTradingOrderNo(tradingVo1.getTradingOrderNo());
        billMapper.updateByPrimaryKey(bill);
        return tradingVo1;
    }


    /**
     * 线上支付
     * @param tradingOrderNos 交易号
     */
    @Override
    public void payOrder(List<Long> tradingOrderNos) {
        billMapper.batchUpdateByTradingOrderNoSelective(tradingOrderNos, BillStatus.PAY.getOrdinal());
    }

    /**
     * 线上退款
     * @param tradingOrderNos 交易号
     */
    @Override
    public void refundOrder(List<Long> tradingOrderNos) {
        billMapper.batchUpdateByTradingOrderNoSelective(tradingOrderNos, BillStatus.CLOSE.getOrdinal());
    }

}
